"""
消息处理器模块，处理来自不同数据源的消息
"""
import time
import json
import uuid
import threading
from typing import Dict, Any, Optional, List, Callable, Set, Tuple
from datetime import datetime, timedelta
from app.utils.logger import Logger
from app.services.service_locator import ServiceLocator
from app.services.comment_service import ICommentService


# 平台名称标准化映射
PLATFORM_MAPPING = {
    "websocket": "websocket",
    "wechat_channel": "wechat_channel",
    "wechat": "wechat_channel",
    "wx": "wechat_channel",
    "wx_live": "wechat_channel",
    "socket": "websocket",
    "sock": "websocket",
    "unknown": "unknown"
}


class MessageCache:
    """消息缓存，用于防止重复消息处理"""
    
    def __init__(self, max_size=1000, expire_time=2, cleanup_interval=2):
        """
        初始化消息缓存
        
        @param max_size: 缓存最大容量
        @param expire_time: 消息过期时间（秒）
        @param cleanup_interval: 定期清理间隔（秒），默认2秒
        """
        self.cache = {}  # 消息缓存字典
        self.max_size = max_size  # 缓存最大容量
        self.expire_time = expire_time  # 消息过期时间（秒）
        self.cleanup_interval = cleanup_interval  # 定期清理间隔
        self.logger = Logger.get_logger("message_cache")
        self.last_cleanup = datetime.now()  # 上次清理时间
        self.cache_lock = threading.Lock()  # 缓存锁，防止并发访问冲突
        
        # 按添加顺序保存键，用于高效清理
        self.key_queue = []
    
    def generate_key(self, data, source):
        """
        根据消息数据生成唯一键
        
        @param data: 消息数据
        @param source: 消息来源
        @return: 唯一键
        """
        # 根据消息类型生成不同的键
        msg_type = data.get('type', '')
        
        # 添加来源前缀，确保不同来源的相似消息能区分
        key_prefix = f"{source}:"
        
        if msg_type == 'comment':
            # 评论消息使用用户ID+内容+时间戳作为键
            user_id = data.get('user_id', data.get('userId', ''))
            nickname = data.get('nickname', '')
            content = data.get('content', '')
            timestamp = data.get('timestamp', '')
            
            # 统一处理时间戳，按2秒间隔标准化
            try:
                # 尝试将任何类型的timestamp转换为整数
                if isinstance(timestamp, (int, float)):
                    # 按2秒间隔标准化时间戳
                    timestamp_sec = int(timestamp // 2) * 2
                elif isinstance(timestamp, str) and timestamp.strip():
                    if timestamp.isdigit():
                        timestamp_sec = int(int(timestamp) // 2) * 2
                    else:
                        # 尝试解析其他格式的字符串时间戳
                        timestamp_sec = int(int(float(timestamp)) // 2) * 2
                else:
                    # 如果没有时间戳，则完全不依赖时间戳去重
                    # 仅使用用户ID/昵称+内容
                    return f"{key_prefix}comment:{user_id or nickname}:{content[:30]}"
                
                # 使用标准化后的时间戳生成键
                return f"{key_prefix}comment:{user_id or nickname}:{content[:30]}:{timestamp_sec}"
            except (ValueError, TypeError):
                # 无法处理的时间戳，使用用户ID和内容作为键
                self.logger.debug(f"无法处理的时间戳格式: {timestamp}，不使用时间戳")
                return f"{key_prefix}comment:{user_id or nickname}:{content[:30]}"
        
        elif msg_type == 'join' or msg_type == 'enter':
            # 进入消息使用用户ID+时间戳作为键
            user_id = data.get('user_id', data.get('userId', ''))
            nickname = data.get('nickname', '')
            timestamp = data.get('timestamp', '')
            
            # 统一处理时间戳
            try:
                if isinstance(timestamp, (int, float)):
                    timestamp_norm = str(int(timestamp))
                elif isinstance(timestamp, str) and timestamp.strip():
                    if timestamp.isdigit():
                        timestamp_norm = timestamp
                    else:
                        timestamp_norm = str(int(float(timestamp)))
                else:
                    timestamp_norm = "0"
                    
                return f"{key_prefix}join:{user_id or nickname}:{timestamp_norm}"
            except (ValueError, TypeError):
                return f"{key_prefix}join:{user_id or nickname}:{timestamp}"
        
        elif msg_type == 'gift':
            # 礼物消息使用用户ID+礼物ID+时间戳作为键
            user_id = data.get('user_id', data.get('userId', ''))
            nickname = data.get('nickname', '')
            gift_id = data.get('gift_id', '')
            gift_name = data.get('gift_name', '')
            content = data.get('content', '')
            timestamp = data.get('timestamp', '')
            
            # 统一处理时间戳
            try:
                if isinstance(timestamp, (int, float)):
                    timestamp_norm = str(int(timestamp))
                elif isinstance(timestamp, str) and timestamp.strip():
                    if timestamp.isdigit():
                        timestamp_norm = timestamp
                    else:
                        timestamp_norm = str(int(float(timestamp)))
                else:
                    timestamp_norm = "0"
                    
                return f"{key_prefix}gift:{user_id or nickname}:{gift_id or gift_name}:{content[:15]}:{timestamp_norm}"
            except (ValueError, TypeError):
                return f"{key_prefix}gift:{user_id or nickname}:{gift_id or gift_name}:{content[:15]}:{timestamp}"
        
        elif msg_type == 'like':
            # 点赞消息使用用户ID+点赞数+时间戳作为键
            user_id = data.get('user_id', data.get('userId', ''))
            nickname = data.get('nickname', '')
            like_count = data.get('like_count', 1)
            timestamp = data.get('timestamp', '')
            
            # 统一处理时间戳
            try:
                if isinstance(timestamp, (int, float)):
                    timestamp_norm = str(int(timestamp))
                elif isinstance(timestamp, str) and timestamp.strip():
                    if timestamp.isdigit():
                        timestamp_norm = timestamp
                    else:
                        timestamp_norm = str(int(float(timestamp)))
                else:
                    timestamp_norm = "0"
                    
                return f"{key_prefix}like:{user_id or nickname}:{like_count}:{timestamp_norm}"
            except (ValueError, TypeError):
                return f"{key_prefix}like:{user_id or nickname}:{like_count}:{timestamp}"
            
        elif msg_type == 'follow':
            # 关注消息使用用户ID+时间戳作为键
            user_id = data.get('user_id', data.get('userId', ''))
            nickname = data.get('nickname', '')
            timestamp = data.get('timestamp', '')
            
            # 统一处理时间戳
            try:
                if isinstance(timestamp, (int, float)):
                    timestamp_norm = str(int(timestamp))
                elif isinstance(timestamp, str) and timestamp.strip():
                    if timestamp.isdigit():
                        timestamp_norm = timestamp
                    else:
                        timestamp_norm = str(int(float(timestamp)))
                else:
                    timestamp_norm = "0"
                    
                return f"{key_prefix}follow:{user_id or nickname}:{timestamp_norm}"
            except (ValueError, TypeError):
                return f"{key_prefix}follow:{user_id or nickname}:{timestamp}"
            
        elif msg_type == 'live_status':
            # 直播状态消息使用直播状态+时间戳作为键
            live_status = data.get('live_status', 0)
            timestamp = data.get('timestamp', '')
            title = data.get('title', '')[:10]  # 使用标题的前10个字符作为区分
            
            # 统一处理时间戳 - 每30秒只处理一次相同状态的消息
            try:
                if isinstance(timestamp, (int, float)):
                    normalized_timestamp = int(timestamp / 30) * 30
                elif isinstance(timestamp, str) and timestamp.strip():
                    if timestamp.isdigit():
                        normalized_timestamp = int(int(timestamp) / 30) * 30
                    else:
                        normalized_timestamp = int(int(float(timestamp)) / 30) * 30
                else:
                    # 使用当前时间作为后备方案
                    normalized_timestamp = int(time.time() / 30) * 30
                
                return f"{key_prefix}live_status:{live_status}:{title}:{normalized_timestamp}"
            except (ValueError, TypeError):
                # 处理异常情况，使用当前时间
                normalized_timestamp = int(time.time() / 30) * 30
                return f"{key_prefix}live_status:{live_status}:{title}:{normalized_timestamp}"
        
        # 默认使用消息类型+时间戳作为键
        timestamp = data.get('timestamp', datetime.now().timestamp())
        
        # 统一处理时间戳
        try:
            if isinstance(timestamp, (int, float)):
                timestamp_norm = str(int(timestamp))
            elif isinstance(timestamp, str) and timestamp.strip():
                if timestamp.isdigit():
                    timestamp_norm = timestamp
                else:
                    timestamp_norm = str(int(float(timestamp)))
            else:
                # 使用当前时间作为后备方案
                timestamp_norm = str(int(time.time()))
                
            return f"{key_prefix}{msg_type}:{timestamp_norm}"
        except (ValueError, TypeError):
            # 回退到简单的字符串表示
            return f"{key_prefix}{msg_type}:{str(timestamp)}"
    
    def add_message(self, data, source="unknown"):
        """
        添加消息到缓存
        
        @param data: 消息数据
        @param source: 消息来源
        @return: 如果是新消息返回True，重复消息返回False
        """
        with self.cache_lock:
            # 每次添加消息时都检查过期消息
            now = datetime.now()
            self._clean_expired()
            self.last_cleanup = now
            
            # 生成消息键
            key = self.generate_key(data, source)
            
            # 检查是否已存在
            if key in self.cache:
                self.logger.debug(f"消息已存在: {key}")
                return False
            
            # 添加到缓存
            self.cache[key] = now
            self.key_queue.append(key)
            
            # 如果缓存过大，删除最早的消息
            if len(self.cache) > self.max_size:
                # 直接从队列开始删除，避免排序
                items_to_remove = int(self.max_size * 0.1)  # 删除10%
                for _ in range(items_to_remove):
                    if self.key_queue:
                        old_key = self.key_queue.pop(0)
                        if old_key in self.cache:
                            del self.cache[old_key]
                
                self.logger.debug(f"缓存清理: 删除了最早的 {items_to_remove} 条消息")
            
            return True
    
    def _clean_expired(self):
        """清理过期消息"""
        now = datetime.now()
        expired_keys = []
        
        # 使用列表推导式查找过期消息，提高效率
        expired_keys = [
            key for key, timestamp in self.cache.items()
            if (now - timestamp).total_seconds() > self.expire_time
        ]
        
        # 删除过期消息
        for key in expired_keys:
            del self.cache[key]
            # 也从队列中移除
            if key in self.key_queue:
                self.key_queue.remove(key)
        
        if expired_keys:
            self.logger.debug(f"缓存清理: 删除了 {len(expired_keys)} 条过期消息")


class MessageProcessor:
    """消息处理器，负责处理来自不同数据源的消息"""
    
    _instance = None  # 单例实例
    _instance_lock = threading.Lock()  # 确保线程安全的创建单例
    
    @classmethod
    def get_instance(cls):
        """获取单例实例（线程安全）"""
        if cls._instance is None:
            with cls._instance_lock:
                if cls._instance is None:
                    cls._instance = MessageProcessor()
        return cls._instance
    
    def __init__(self):
        """初始化消息处理器"""
        self.logger = Logger.get_logger("message_processor")
        self._listeners = []  # 消息监听器列表
        self.message_cache = MessageCache()  # 消息缓存，用于过滤重复消息
        self._retry_queue = []  # 重试队列，存储处理失败的消息
        self._field_mappings = self._init_field_mappings()  # 初始化字段映射关系
        self._process_lock = threading.Lock()  # 处理锁，防止并发处理冲突
        self.processed_count = 0  # 处理消息计数
        self.logger.info("消息处理器初始化完成")
    
    def _init_field_mappings(self):
        """初始化字段名称映射关系"""
        return {
            "nickname": ["nickname", "user_name", "username", "nick", "name"],
            "user_id": ["user_id", "userId", "uid", "id", "openid"],
            "content": ["content", "text", "message", "msg"],
            "timestamp": ["timestamp", "time", "create_time", "createTime", "create_at"],
            "gift_name": ["gift_name", "giftName", "gift"],
            "gift_count": ["gift_count", "giftCount", "gift_num", "giftNum", "count", "num"],
            "like_count": ["like_count", "likeCount", "like_num", "likeNum", "likes"]
        }
    
    def process_message(self, message: Dict[str, Any], source: str = "unknown"):
        """
        处理消息
        
        @param message: 消息数据
        @param source: 消息来源，用于日志记录和调试
        @return: 处理后的消息ID
        """
        # 使用处理锁确保线程安全
        with self._process_lock:
            try:
                # 生成消息ID用于追踪
                message_id = str(uuid.uuid4())
                process_time = datetime.now()
                
                # 特别记录视频号消息
                if source == "wechat_channel":
                    self.logger.info(f"[{message_id}] 收到来自视频号的消息: {json.dumps(message, ensure_ascii=False)}")
                else:
                    # 记录接收到的消息
                    self.logger.debug(f"[{message_id}] 收到来自 {source} 的消息: {json.dumps(message, ensure_ascii=False)}")
                
                # 验证消息格式
                if not self._validate_message(message):
                    self.logger.warning(f"[{message_id}] 消息格式无效: {json.dumps(message, ensure_ascii=False)}")
                    return None
                
                # 过滤重复消息
                if not self.message_cache.add_message(message, source):
                    return None
                
                # 标准化消息格式
                standardized_message = self._standardize_message(message, source)
                
                # 添加追踪信息
                standardized_message["message_id"] = message_id
                standardized_message["process_time"] = int(process_time.timestamp())
                
                # 记录标准化后的消息
                # if source == "wechat_channel":
                #     self.logger.info(f"[{message_id}] 标准化后的视频号消息: {json.dumps(standardized_message, ensure_ascii=False)}")
                
                # 增加处理计数
                self.processed_count += 1
                
                # 获取评论服务
                comment_service = ServiceLocator.get(ICommentService)
                if comment_service:
                    try:
                        # 转发给评论服务处理
                        comment_service.process_message(standardized_message)
                        # if source == "wechat_channel":
                        #     self.logger.info(f"[{message_id}] 视频号消息已转发给评论服务")
                        # else:
                        #     self.logger.debug(f"[{message_id}] 消息已转发给评论服务: {json.dumps(standardized_message, ensure_ascii=False)}")
                    except Exception as e:
                        # 处理失败，加入重试队列
                        self.logger.error(f"[{message_id}] 评论服务处理消息失败: {str(e)}")
                        self._add_to_retry_queue(standardized_message)
                else:
                    self.logger.warning(f"[{message_id}] 评论服务不可用，无法处理消息")
                    # 评论服务不可用时，保存到重试队列
                    self._add_to_retry_queue(standardized_message)
                
                # 通知监听器
                listener_count = 0
                for listener in self._listeners:
                    try:
                        listener(standardized_message)
                        listener_count += 1
                    except Exception as e:
                        self.logger.error(f"[{message_id}] 通知监听器时出错: {str(e)}")
                
                # if source == "wechat_channel":
                #     self.logger.info(f"[{message_id}] 视频号消息已通知 {listener_count} 个监听器")
                
                return message_id
            except Exception as e:
                self.logger.error(f"处理消息时出错: {str(e)}")
                import traceback
                self.logger.error(f"异常堆栈: {traceback.format_exc()}")
                return None
    
    def _add_to_retry_queue(self, message):
        """添加消息到重试队列"""
        # 限制重试队列大小，防止内存溢出
        if len(self._retry_queue) >= 100:
            self._retry_queue.pop(0)  # 移除最早的消息
        
        # 添加到队列尾部
        self._retry_queue.append((message, datetime.now()))
        self.logger.debug(f"消息已加入重试队列: {message.get('message_id')}")
    
    def retry_failed_messages(self):
        """尝试重新处理失败的消息"""
        if not self._retry_queue:
            return 0
            
        retry_count = 0
        now = datetime.now()
        remaining_messages = []
        
        for message, timestamp in self._retry_queue:
            # 只重试60秒前的消息，避免频繁重试
            if (now - timestamp).total_seconds() > 60:
                comment_service = ServiceLocator.get(ICommentService)
                if comment_service:
                    try:
                        comment_service.process_message(message)
                        self.logger.info(f"成功重新处理消息: {message.get('message_id')}")
                        retry_count += 1
                    except Exception as e:
                        self.logger.error(f"重新处理消息失败: {str(e)}")
                        # 更新时间戳，避免频繁重试
                        remaining_messages.append((message, now))
                else:
                    # 服务仍不可用，保留在队列中
                    remaining_messages.append((message, timestamp))
            else:
                # 消息太新，保留在队列中
                remaining_messages.append((message, timestamp))
        
        # 更新重试队列
        self._retry_queue = remaining_messages
        return retry_count
    
    def add_listener(self, listener: Callable[[Dict[str, Any]], None]):
        """
        添加消息监听器
        
        @param listener: 监听器回调函数
        """
        if listener not in self._listeners:
            self._listeners.append(listener)
            self.logger.debug(f"已添加消息监听器: {listener.__name__ if hasattr(listener, '__name__') else str(listener)}")
    
    def remove_listener(self, listener: Callable[[Dict[str, Any]], None]):
        """
        移除消息监听器
        
        @param listener: 监听器回调函数
        """
        if listener in self._listeners:
            self._listeners.remove(listener)
            self.logger.debug(f"已移除消息监听器: {listener.__name__ if hasattr(listener, '__name__') else str(listener)}")
    
    def _validate_message(self, message: Dict[str, Any]) -> bool:
        """
        验证消息格式
        
        @param message: 消息数据
        @return: 是否有效
        """
        # 检查必要字段
        if not isinstance(message, dict):
            return False
            
        # 至少需要包含type字段
        if "type" not in message:
            return False
            
        return True
    
    def _standardize_message(self, message: Dict[str, Any], source: str) -> Dict[str, Any]:
        """
        标准化消息格式
        
        @param message: 原始消息
        @param source: 消息来源
        @return: 标准化后的消息
        """
        # 创建一个新字典，避免修改原始消息
        standardized = message.copy()
        
        # 标准化平台名称
        if "platform" not in standardized:
            norm_source = source.lower()
            standardized["platform"] = PLATFORM_MAPPING.get(norm_source, norm_source)
        
        # 设置时间戳
        if "timestamp" not in standardized:
            standardized["timestamp"] = int(time.time())
        
        # 标准化消息字段名称
        self._map_standard_fields(standardized)
        
        # 标准化用户名字段 - 确保同时存在nickname和user_name
        if "nickname" in standardized and "user_name" not in standardized:
            standardized["user_name"] = standardized["nickname"]
        elif "user_name" in standardized and "nickname" not in standardized:
            standardized["nickname"] = standardized["user_name"]
        
        # 根据消息类型进行特定处理
        msg_type = standardized.get("type", "unknown")
        
        if msg_type == "comment":
            # 确保评论消息有内容字段
            if "content" not in standardized:
                standardized["content"] = ""
                
        elif msg_type == "gift":
            # 确保礼物消息有礼物名称和数量
            if "gift_name" not in standardized:
                standardized["gift_name"] = "未知礼物"
            if "gift_count" not in standardized:
                standardized["gift_count"] = 1
        
        elif msg_type == "join" or msg_type == "enter":
            # 统一进入消息类型为join
            standardized["type"] = "join"
            # 确保进入消息有必要字段
            if "join_time" not in standardized:
                standardized["join_time"] = standardized["timestamp"]
        
        elif msg_type == "like":
            # 确保点赞消息有点赞数量
            if "like_count" not in standardized:
                standardized["like_count"] = 1
        
        elif msg_type == "follow":
            # 确保关注消息有必要字段
            if "follow_time" not in standardized:
                standardized["follow_time"] = standardized["timestamp"]
        
        elif msg_type == "live_status":
            # 确保直播状态消息有必要字段
            if "live_status" not in standardized:
                standardized["live_status"] = 0
            if "view_count" not in standardized:
                standardized["view_count"] = 0
            if "like_count" not in standardized:
                standardized["like_count"] = 0
        
        return standardized
    
    def _map_standard_fields(self, data):
        """将不同名称的字段映射到标准字段名称"""
        # 根据字段映射关系，复制字段内容
        for target_field, source_fields in self._field_mappings.items():
            # 如果标准字段名已存在，不需要映射
            if target_field in data:
                continue
                
            # 尝试从源字段中获取值
            for source_field in source_fields:
                if source_field in data and source_field != target_field:
                    data[target_field] = data[source_field]
                    break
    
    def get_statistics(self):
        """
        获取消息处理器统计信息
        
        @return: 统计信息字典
        """
        return {
            "processed_count": self.processed_count,
            "retry_queue_size": len(self._retry_queue),
            "cache_size": len(self.message_cache.cache if hasattr(self.message_cache, 'cache') else {}),
            "listeners_count": len(self._listeners)
        } 